package com.hunter.jerrymouse.server.netty;

import com.alibaba.fastjson.JSON;
import com.hunter.jerrymouse.annotation.request.RequestRouter;
import com.hunter.jerrymouse.annotation.response.ResponseData;
import com.hunter.jerrymouse.constants.ResultConstants;
import com.hunter.jerrymouse.request.RequestHandle;
import com.hunter.jerrymouse.request.RequestHandleContext;
import com.hunter.jerrymouse.request.netty.NettyRequest;
import com.hunter.jerrymouse.request.netty.RequestParamParser;
import com.hunter.jerrymouse.util.SpringBeanUtils;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * Created by hunter
 * <p>
 * http请求处理器, 每个请求过来都会有一个新的HttpRequestHandleServer来处理
 */
public class HttpRequestHandleServer extends ChannelInboundHandlerAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpRequestHandleServer.class);

    /**
     * 给200个线程池处理请求, 类似tomcat
     */
    private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(200);

    /**
     * 当前请求解析
     */
    private NettyRequest request;

    @Override
    public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
        if (request != null && request.getHandled()) {
            return;
        }

        FullHttpRequest httpRequest = (FullHttpRequest) msg;
        // 封装为自己的类
        this.request = new NettyRequest(httpRequest);
        // 预检查
        preCheckHttpRequest();
        // 填充基础参数
        this.request.setReceivedTime(System.currentTimeMillis());
        // 读取一次就会清空, 这里wrapper一个, 用自己的, 攒劲
        this.request.setRequestData(Unpooled.wrappedBuffer(httpRequest.content()));

        // 采用线程池处理, 不阻塞主处理线程
        EXECUTOR.submit(() -> {
            handleRequest(ctx, request);

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Resolve request : {} , cost time : {} ms", request.getUri(), System.currentTimeMillis() - request.getReceivedTime());
            }
        });
    }

    /**
     * 处理请求主体
     *
     * @param ctx
     * @param request
     */
    private void handleRequest(final ChannelHandlerContext ctx, final NettyRequest request) {
        request.setHandled(true);
        try {
            RequestHandleContext requestHandleContext = SpringBeanUtils.getBean(RequestHandleContext.class);
            RequestHandle requestHandle = requestHandleContext.getRequestHandle(request.getRealUri());

            // 拿到对应的处理实例类
            Object handleObject = SpringBeanUtils.getBean(requestHandle.getClazz());
            if (handleObject == null) {
                writeResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, request, ResultConstants.NO_HANDLE.getBytes());
                return;
            }
            // 反射调用
            Object[] args = RequestParamParser.parseRequestParameters(ctx, requestHandle, request);
            Object result = requestHandle.getMethod().invoke(handleObject, args);
            writeResponse(ctx, HttpResponseStatus.OK, request, result);
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            // 处理反射的异常不等同于一般异常
            Throwable cause = e.getCause();
            if (cause != null) {
                exceptionCaught(ctx, cause);
            } else {
                exceptionCaught(ctx, e);
            }
        } catch (Exception e) {
            exceptionCaught(ctx, e);
        }
    }

    /**
     * 响应请求
     *
     * @param ctx
     */
    private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, NettyRequest request, Object object) {
        if (!ctx.channel().isOpen()) {
            return;
        }

        RequestHandleContext requestHandleContext = SpringBeanUtils.getBean(RequestHandleContext.class);
        RequestHandle requestHandle = requestHandleContext.getRequestHandle(request.getRealUri());
        ResponseData.ContentType contentType;
        if (requestHandle == null) {
            contentType = ResponseData.ContentType.TEXT_PLAIN;// default~
        } else {
            contentType = requestHandle.getResponseContentType();
        }
        byte[] resultBytes;
        if (object == null) {
            resultBytes = null;
        } else if (object instanceof String) {
            resultBytes = object.toString().getBytes();
        } else if (ResponseData.ContentType.APPLICATION_JSON.equals(contentType)) {
            resultBytes = JSON.toJSONString(object).getBytes();
        } else {
            resultBytes = object.toString().getBytes();
        }

        writeResponse(ctx, status, request, resultBytes, contentType.getName());
    }


    /**
     * 响应请求
     *
     * @param ctx
     */
    private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, NettyRequest request, byte[] data, String contentType) {
        if (!ctx.channel().isOpen()) {
            return;
        }

        // 兼容返回null数据
        if (data == null) {
            data = new byte[0];
        }

        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.wrappedBuffer(data));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
        response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());

        boolean keepAlive = HttpUtil.isKeepAlive(request);
        if (keepAlive) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        LOGGER.error("Invoke request handle {} error .", request.getUri(), cause);
        writeResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, request, cause.getMessage());
        ctx.close();
    }

    private void preCheckHttpRequest() {
        // 404请求
        RequestHandleContext requestHandleContext = SpringBeanUtils.getBean(RequestHandleContext.class);
        RequestHandle requestHandle = requestHandleContext.getRequestHandle(request.getRealUri());
        if (requestHandle == null) {
            request.setHandled(true);
            // TODO 暂用runtime异常处理
            throw new RuntimeException(ResultConstants.NOT_FOUND);
        }

        // 方法支持检查
        RequestRouter.Method[] supportMethods = requestHandle.getSupportHttpMethod();
        if (supportMethods != null && supportMethods.length > 0
                && Arrays.stream(supportMethods).noneMatch(m -> m.getName().equals(request.getMethod().toString()))) {
            request.setHandled(true);
            // TODO 暂用runtime异常处理
            throw new RuntimeException(ResultConstants.HTTP_METHOD_NOT_SUPPORT);
        }
    }
}
